#pragma once

#include "common.hpp"
#include "data.hpp"
#include <errno.h>
#include <iostream>
#include <memory>
#include <modbus.h>
#include <mutex>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>

class ModbusRtu {
private:
    std::mutex _mutex;

private:
    struct Modbus_t {
        modbus_t *_handler = nullptr;

        bool connect(uint8_t slaveAddr, const std::string &port, int baudrate, char parity, int databits, int stopbits);

        Data read(uint16_t regAddr, uint16_t regLen);

        Data readInputReg(uint16_t regAddr, uint16_t regLen);

        /* write single */
        bool write(uint16_t regAddr, uint16_t value);
        /* write multiple */
        bool write(uint16_t regAddr, uint16_t regLen, const std::vector<uint8_t> &data);
        bool write(uint16_t regAddr, uint16_t regLen, const std::vector<uint16_t> &data);

        bool writeBit(uint16_t regAddr, uint16_t value);

        Data reportId(int maxDest);

        ~Modbus_t(); /* 释放资源 */

        Modbus_t() = default;
    };

public:
    /* 0x03 */
    Data readRegisters(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::string &port, int baudrate, char parity, int databits, int stopbits);
    /* 0x02 */
    Data readInputBits(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::string &port, int baudrate, char parity, int databits, int stopbits);
    /* 0x05 */
    bool writeBit(uint8_t slaveAddr, uint16_t regAddr, uint16_t value, const std::string &port, int baudrate, char parity, int databits, int stopbits);
    /* 0x06 */
    bool writeRegister(uint8_t slaveAddr, uint16_t regAddr, uint16_t value, const std::string &port, int baudrate, char parity, int databits, int stopbits);
    /* 0x11 */
    Data reportId(uint8_t slaveAddr, const std::string &port, int baudrate, char parity, int databits, int stopbits);
    /* 0x10 */
    bool writeRegisters(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::vector<uint8_t> &data, const std::string &port, int baudrate, char parity, int databits, int stopbits);
    bool writeRegisters(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::vector<uint16_t> &data, const std::string &port, int baudrate, char parity, int databits, int stopbits);
};

bool ModbusRtu::Modbus_t::connect(uint8_t slaveAddr, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    _handler = modbus_new_rtu(port.c_str(), baudrate, std::toupper(parity), databits, stopbits);
    if (!_handler) {
        modbus_error("modbus new rtu failed {}", modbus_strerror(errno));
        return false;
    }

    modbus_set_debug(_handler, TRUE);
    // modbus_set_byte_timeout(_handler, 1, 0);
    modbus_set_slave(_handler, slaveAddr);

    if (modbus_connect(_handler) < 0) {
        modbus_error("modbus Connect failed: {}", modbus_strerror(errno));
        return false;
    }

    // modbus_flush(_handler);

    return true;
}

Data ModbusRtu::Modbus_t::read(uint16_t regAddr, uint16_t regLen)
{
    if (!_handler) {
        modbus_error("can't connect, please connect");
        return Data {};
    }

    auto buffer = std::make_unique<uint16_t[]>(regLen);
    auto ret = modbus_read_registers(_handler, regAddr, regLen, buffer.get());
    if (ret <= 0) {
        modbus_error("modbus read failed: {}", modbus_strerror(errno));
        return Data {};
    }

    return Data(buffer.get(), regLen);
}

Data ModbusRtu::Modbus_t::readInputReg(uint16_t regAddr, uint16_t regLen)
{
    if (!_handler) {
        modbus_error("can't connect, please connect");
        return Data {};
    }

    auto buffer = std::make_unique<uint8_t[]>(regLen);
    auto ret = modbus_read_input_bits(_handler, regAddr, regLen, buffer.get());
    if (ret <= 0) {
        modbus_error("modbus read failed: {}", modbus_strerror(errno));
        return Data {};
    }

    return Data(buffer.get(), regLen);
}

Data ModbusRtu::Modbus_t::reportId(int maxDest = 64)
{
    if (!_handler) {
        modbus_error("can't connect, please connect");
        return Data {};
    }

    auto buffer = std::make_unique<unsigned char[]>(maxDest);
    auto ret = modbus_report_slave_id(_handler, maxDest, buffer.get());
    if (ret <= 0) {
        modbus_error("report slave id failed {}", modbus_strerror(errno));
        return Data {};
    }

    return Data(buffer.get(), maxDest);
}

bool ModbusRtu::Modbus_t::write(uint16_t regAddr, uint16_t value)
{
    if (!_handler) {
        modbus_error("can't connect, please connect");
        return false;
    }

    auto ret = modbus_write_register(_handler, regAddr, value);
    if (ret <= 0) {
        modbus_error("modbus write error : {}", modbus_strerror(errno));
        return false;
    }

    return true;
}

bool ModbusRtu::Modbus_t::write(uint16_t regAddr, uint16_t regLen, const std::vector<uint8_t> &data)
{
    if (!_handler) {
        modbus_error("can't connect, please connect");
        return false;
    }

    if (data.size() != regLen * 2) {
        modbus_error("data invaild, please check");
        return false;
    }

    std::vector<uint16_t> sendData;

    for (int i = 0; i < regLen; i++) {
        auto value = U8_TO_U16(data[2 * i], data[2 * i + 1]);
        sendData.emplace_back(value);
    }

    auto ret = modbus_write_registers(_handler, regAddr, regLen, sendData.data());
    if (ret <= 0) {
        modbus_error("modbus write error : {}", modbus_strerror(errno));
        return false;
    }

    return true;
}

bool ModbusRtu::Modbus_t::write(uint16_t regAddr, uint16_t regLen, const std::vector<uint16_t> &data)
{
    if (!_handler) {
        modbus_error("can't connect, please connect");
        return false;
    }

    if (data.size() != regLen) {
        modbus_error("data invaild, please check");
        return false;
    }

    auto ret = modbus_write_registers(_handler, regAddr, regLen, data.data());
    if (ret <= 0) {
        modbus_error("modbus write error : {}", modbus_strerror(errno));
        return false;
    }

    return true;
}

bool ModbusRtu::Modbus_t::writeBit(uint16_t regAddr, uint16_t value)
{
    if (!_handler) {
        modbus_error("can't connect, please connect");
        return false;
    }

    auto ret = modbus_write_bit(_handler, regAddr, value);
    if (ret <= 0) {
        modbus_error("modbus write bit error : {}", modbus_strerror(errno));
        return false;
    }

    return true;
}

ModbusRtu::Modbus_t::~Modbus_t()
{
    if (_handler) {
        modbus_close(_handler);
        modbus_free(_handler);
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(5));
}

Data ModbusRtu::readRegisters(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    std::unique_lock<std::mutex> _lock(_mutex);

    Modbus_t modbusHandler;
    if (!modbusHandler.connect(slaveAddr, port, baudrate, parity, databits, stopbits)) {
        return Data {};
    }

    return modbusHandler.read(regAddr, regLen);
}

Data ModbusRtu::readInputBits(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    std::unique_lock<std::mutex> _lock(_mutex);

    Modbus_t modbusHandler;
    if (!modbusHandler.connect(slaveAddr, port, baudrate, parity, databits, stopbits)) {
        return Data {};
    }

    return modbusHandler.readInputReg(regAddr, regLen);
}

bool ModbusRtu::writeRegister(uint8_t slaveAddr, uint16_t regAddr, uint16_t value, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    std::unique_lock<std::mutex> _lock(_mutex);

    Modbus_t modbusHandler;
    if (!modbusHandler.connect(slaveAddr, port, baudrate, parity, databits, stopbits)) {
        return false;
    }

    return modbusHandler.write(regAddr, value);
}

Data ModbusRtu::reportId(uint8_t slaveAddr, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    std::unique_lock<std::mutex> _lock(_mutex);

    Modbus_t modbusHandler;
    if (!modbusHandler.connect(slaveAddr, port, baudrate, parity, databits, stopbits)) {
        return Data {};
    }

    return modbusHandler.reportId();
}

bool ModbusRtu::writeRegisters(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::vector<uint8_t> &data, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    std::unique_lock<std::mutex> _lock(_mutex);

    Modbus_t modbusHandler;
    if (!modbusHandler.connect(slaveAddr, port, baudrate, parity, databits, stopbits)) {
        return false;
    }

    return modbusHandler.write(regAddr, regLen, data);
}

bool ModbusRtu::writeRegisters(uint8_t slaveAddr, uint16_t regAddr, uint16_t regLen, const std::vector<uint16_t> &data, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    std::unique_lock<std::mutex> _lock(_mutex);

    Modbus_t modbusHandler;
    if (!modbusHandler.connect(slaveAddr, port, baudrate, parity, databits, stopbits)) {
        return false;
    }

    return modbusHandler.write(regAddr, regLen, data);
}

bool ModbusRtu::writeBit(uint8_t slaveAddr, uint16_t regAddr, uint16_t value, const std::string &port, int baudrate, char parity, int databits, int stopbits)
{
    std::unique_lock<std::mutex> _lock(_mutex);

    Modbus_t modbusHandler;
    if (!modbusHandler.connect(slaveAddr, port, baudrate, parity, databits, stopbits)) {
        return false;
    }

    return modbusHandler.writeBit(regAddr, value);
}
